【Java设计模式 细节一】单例模式

为何需要单例模式

对于系统中的某些类来说,只有一个实例很重要,例如,系统对于接口缓存的设计,只能拥有一个文件系统。

单例模式设计要点

  • 保证该类只有一个实例。将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象
  • 提供一个该实例的访问点。一般由该类自己负责创建实例,并提供一个静态方法作为该实例的访问点

静态 & 非静态

  • 静态声明实例引用时即实例化
  • 非静态方法第一次被调用前不实例化,称为懒加载。对于创建大,不一定被使用时,使用懒加载,节约了大量开销。

实现单例模式的九种方法

线程不安全的非静态方法 - 多线程不可用

package com.cw.amt;
public class Example1 {
private static Example1 INSTANCE;
private Example1() {};
public static Example1Example1 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Example1();
}
return INSTANCE;
}
}
  • 优点:实现了lazy loading。
  • 缺点:只有在单线程下能保证只有一个实例,多线程下有创建多个实例的风险

同步方法下的非静态方法 - 可用,不推荐

package com.cw.amt;
public class Example2 {
private static Example2 INSTANCE;
private Example2() {};
public static synchronized Example2 getInstance() {
if (INSTANCE == null) {
INSTANCEINSTANCE = new Example2();
}
return INSTANCE;
}
}
  • 优点:线程安全,可确保正常使用下(不考虑通过反射调用私有构造方法)只有一个实例
  • 缺点:每次获取实例都需要申请锁,开销大,效率低

同步代码块下的非静态方法 - 不可用

package com.cw.amt;
public class Example3 {
private static Example3 INSTANCE;
private Example3() {};
public static Example3 getInstance() {
if (INSTANCE == null) {
synchronized (Example3.class) {
INSTANCE = new Example3();
}
}
return INSTANCE;
}
}
  • 优点:不需要在每次调用时加锁,效率比上一个高。
  • 缺点:虽然使用synchronized,但本质上是线程不安全的。

双重检查(Double Check)下的非静态方法 - 推荐

package com.cw.amt;
public class Example4 {
private static Example4 INSTANCE;
private Example4() {};
public static Example4 getInstance() {
if (INSTANCE == null) {
synchronized(Example4.class){
if(INSTANCE == null) {
INSTANCE = new Example4();
}
}
}
return INSTANCE;
}
}
  • 优点:使用了双重检查,很大程度上避免了线程不安全,同时也避免了不必要的锁开销。对于保证实例返回只有一个实例时,这里要注意,需要使用volatile关键字来处理
  • 缺点:不完全保证

静态工厂方法 - 推荐

package com.jasongj.singleton6;
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {};
public static Singleton getInstance() {
return INSTANCE;
}
}
  • 优点:实现简单,无线程同步问题
  • 缺点:在类装载时完成实例化。若该实例一直未被使用,则会造成资源浪费

枚举 推荐

对于枚举,因枚举具有单例特性,对比双重检查时,有时候也会返回不止一个实例对象。虽然这种问 题通过改善java内存模型和使用volatile变量可以解决,但是这种方法对于很多初学者来说写起来还是很棘手。相比用 synchronization的双检索实现方式来说,枚举单例就简单多了。你不相信?比较一下下面的双检索实现代码和枚举实现代码就知道了。

用枚举实现的单例

/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
INSTANCE;
}

代码就这么简单,你可以使用EasySingleton.INSTANCE调用它,对比上面使用的双重检查单例例子方法容易多了。

枚举单例可以自己处理序列化

传统的单例模式的另外一个问题是一旦你实现了serializable接口,他们就不再是单例的了,因为readObject()方法总是返回一个 新的实例对象,就像java中的构造器一样。你可以使用readResolve()方法来避免这种情况,

通过像下面的例子中这样用单例来替换新创建的实 例:

//readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}

  • 如果你的单例类包含状态的话就变的更复杂了,你需要把他们置为transient状态,但是用枚举单例的话,序列化就不要考虑了。

枚举单例是线程安全的

由于枚举实例的创建默认就是线程安全的,你不需要担心双检锁问题。

总结

通过提供序列化和线程安全并且几行代码搞定,说明枚举单例模式是java5之后创建单例最好的方法。如果有更好方法可以告诉我(●´∀`●)

文章作者: 陈 武
文章链接: http://www.updatecg.xin/2017/09/17/【Java设计模式 细节一】单例模式/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 我的学习记录
打赏
  • 微信
  • 支付寶

评论